#ifndef ASM_PGTABLE_H
#define ASM_PGTABLE_H

#include <arch/mm.h>
#include <arch/pgtable_hwdef.h>
#include <arch/pgtable_types.h>
#include <arch/barrier.h>

/* 查找PGD索引 */
#define PGD_Index(addr) (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))

/* 通过地址addr查找PGD的表项 */
#define PGD_OffsetRaw(pgd, addr) ((PGD_Type *)(((PGD_Type *)(pgd)) \
			+ PGD_Index(addr)))

#define PGD_Offset(mm, addr) (PGD_OffsetRaw((mm)->pgd, (addr)))

#define PGD_AddrEnd(addr, end)						\
({	unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;	\
	(__boundary - 1 < (end) - 1) ? __boundary : (end);		\
})

#define PUD_AddrEnd(addr, end)						\
({	unsigned long __boundary = ((addr) + PUD_SIZE) & PUD_MASK;	\
	(__boundary - 1 < (end) - 1) ? __boundary : (end);		\
})

#define PMD_AddrEnd(addr, end)						\
({	unsigned long __boundary = ((addr) + PMD_SIZE) & PMD_MASK;	\
	(__boundary - 1 < (end) - 1) ? __boundary : (end);		\
})

#define PGD_None(pgd) (!GetPGD(pgd))
#define PUD_None(pud) (!GetPUD(pud))
#define PMD_None(pmd) (!GetPMD(pmd))

/* 判断是否PMD section映射 */
#define PMD_Section(pmd)	((GetPMD(pmd) & PMD_TYPE_MASK) == \
				 PMD_TYPE_SECT)
/* 判断是否PUD section映射 */
#define PUD_Section(pud)	((GetPUD(pud) & PUD_TYPE_MASK) == \
				 PUD_TYPE_SECT)

static inline unsigned long PGD_PagePhyAddr(PGD_Type pgd)
{
	return GetPGD(pgd) & PTE_ADDR_MASK;
}

#define PUD_Index(addr) ((addr) >> PUD_SHIFT & (PTRS_PER_PUD - 1))

/*
 * 根据pgd表项来找到下一个pud的表项
 * 这里返回pud表项的物理地址
 *
 * 读取pgd可以得到PUD页表的物理基地址，然后根据
 * vaddr来得到PUD索引值，从而得到vaddr对应的
 * pud表项的物理地址
 */
#define PUD_OffsetPhys(pgd, addr) ((PUD_Type *)((PGD_PagePhyAddr(*(pgd)) + \
				PUD_Index(addr) * sizeof(PUD_Type))))

/*
 * 根据pgd表项来找到下一个pud的表项
 * 这里返回pud表项的线性映射的虚拟地址
 *
 * 这里适用于 线性映射已经完成后的 页表查询
 * 和创建
 */
#define PUD_OffsetVirt(pgd, addr) \
	((PUD_Type *)NX_Phy2Virt((unsigned long)PUD_OffsetPhys(pgd, addr)))

static inline unsigned long PUD_PagePhyAddr(PUD_Type pud)
{
	return GetPUD(pud) & PTE_ADDR_MASK;
}

#define PMD_Index(addr) ((addr) >> PMD_SHIFT & (PTRS_PER_PMD - 1))

/*
 * 根据pud表项来找到下一个pmd的表项
 * 这里返回pmd表项的物理地址
 *
 * 读取pud可以得到PMD页表的物理基地址，然后根据
 * vaddr来得到PMD索引值，从而得到vaddr对应的
 * pmd表项的物理地址
 */
#define PMD_OffsetPhys(pud, addr) \
	((PMD_Type *)(PUD_PagePhyAddr(*(pud)) + PMD_Index(addr) * sizeof(PMD_Type)))

/*
 * 根据pud表项来找到下一个pmd的表项
 * 这里返回pmd表项的线性映射的虚拟地址
 *
 * 这里适用于 线性映射已经完成后的 页表查询
 * 和创建
 */
#define PMD_OffsetVirt(pud, addr) \
	((PMD_Type *)NX_Phy2Virt((unsigned long)PMD_OffsetPhys(pud, addr)))

#define PMD_Table(pmd)	((GetPMD(pmd) & PMD_TYPE_MASK) == \
			  PMD_TYPE_TABLE)
#define PMD_Section(pmd)	((GetPMD(pmd) & PMD_TYPE_MASK) == \
			  PMD_TYPE_SECT)

#define PageMakePMD(pfn, prot) (MakePMD(((pfn) << PMD_SHIFT) | (prot)))

static inline unsigned long PMD_PagePhyAddr(PMD_Type pmd)
{
	return GetPMD(pmd) & PTE_ADDR_MASK;
}

#define PTE_Index(addr) (((addr) >> NX_PAGE_SHIFT) & (PTRS_PER_PTE - 1))

/*
 * 根据pmd表项来找到下一个pte的表项
 * 这里返回pte表项的物理地址
 *
 * 读取pmd可以得到PTE页表的物理基地址，然后根据
 * vaddr来得到pte索引值，从而得到vaddr对应的
 * pte表项的物理地址
 */
#define PTE_OffsetPhys(dir, addr) \
	((PTE_Type *)(PMD_PagePhyAddr(*(dir)) + PTE_Index(addr) * sizeof(PTE_Type)))

/*
 * 根据pmd表项来找到下一个pte的表项
 * 这里返回pte表项的线性映射的虚拟地址
 *
 * 这里适用于 线性映射已经完成后的 页表查询
 * 和创建
 */
#define PTE_OffsetVirt(pmd, addr) \
	((PTE_Type *)NX_Phy2Virt((unsigned long)PTE_OffsetPhys(pmd, addr)))

#define PageMakePTE(pfn, prot)  (MakePTE(((pfn) << NX_PAGE_SHIFT) | (prot)))

static inline void SetPGD(PGD_Type *pgdp, PGD_Type pgd)
{
	*pgdp = pgd;

	dsb(ishst);
}

static inline void SetPUD(PUD_Type *pudp, PUD_Type pud)
{
	*pudp = pud;

	dsb(ishst);
}

static inline void SetPMD(PMD_Type *pmdp, PMD_Type pmd)
{
	*pmdp = pmd;

	dsb(ishst);
}

static inline void SetPTE(PTE_Type *ptep, PTE_Type pte)
{
	*ptep = pte;
	dsb(ishst);
}

static inline unsigned long MakeSectionAttr(unsigned long prot)
{
	return prot & ~PTE_TABLE_BIT;
}

#endif /*ASM_PGTABLE_H*/
